﻿/**
 * Quickloop by Stephen Minty (info@itsminty.co.uk)
 *
 * A small dockable panel to help you add one-click loop expressions.
 *
 * Select the property to which you want to add your loop expression and click the appropriate button:
 * 1. loopOut - adds a loopOut Cycle expression by default.
 *      Hold CMD/SHIFT/ALT to apply PingPong/Continue/Offset respectively.
 * 2. loopIn - adds a loopIn Cycle expression by default.
 *      Hold CMD/SHIFT/ALT to apply PingPong/Continue/Offset respectively.
 * 3. loopInfinite - adds an expression which loops your property infinitely both before and after your keyframes. Cycle by default.
 *      Hold CMD/SHIFT/ALT to apply PingPong/Continue/Offset respectively.
 *
 * This is very much a Frankenstein script, cobbled together by dissecting various other free scripts.
 * Huge thanks to Zack Lovatt, whose code heavily informed this script, and whose help made it better.
 * Thank you to Adam Plouff, for the awesome vector icons code.
 * And thanks to James Ronan for his advice.
 */

(function (thisObj) {

   var scriptName = "Quickloop";

   /**
    * UTILITIES
    */

    /**
    * Convert a hex string to an RGBA color array
    *
    * @param {String} hexString  String to convert
    * @returns {Number[]}        RGBA Array
    */
  function hexToArray(hexString) {
    var hexColor = hexString.replace('#', '');
    var r = parseInt(hexColor.slice(0, 2), 16) / 255;
    var g = parseInt(hexColor.slice(2, 4), 16) / 255;
    var b = parseInt(hexColor.slice(4, 6), 16) / 255;
    return [r, g, b, 1];
  }

    /**
    * Runs the callback function on all selected properties
    *
    * @param {CompItem} comp     Composition to run callback in
    * @param {Function} callback Function to run on selected props
    */
  function forSelectedProperties(comp, callback) {
    var selectedProperties = comp.selectedProperties;
    for (var i = 0, il = selectedProperties.length; i < il; i++) {
      callback(selectedProperties[i]);
    }
  }

    /**
    * SCRIPT FUNCTIONS
    */

    /**
    * Adds loop expression to property
    *
    * @param {Property} property    Property to add expression to
    * @param {String} loopDirection Loop direction (loopIn or loopOut)
    * @param {String} loopType      Loop function
    */
  function addLoopExpression(property, loopDirection, loopType) {
    if (!property.canSetExpression) {
      return;
    }

    property.expression = loopDirection + '("' + loopType + '");';
  }

  /**
   * Loops selected properties with given option
   *
   * @param {String} loopDirection Loop direction (loopIn or loopOut)
   */
  function loopSelectedProps(loopDirection) {
    var composition = app.project.activeItem;

    if (!composition || !(composition instanceof CompItem)) {
      alert('Please select composition first');
      return;
    }

    var loopType = 'Cycle';

    // Ctrl = PingPong
    if ((ScriptUI.environment.keyboardState.ctrlKey) || (ScriptUI.environment.keyboardState.metaKey)) {
      loopType = 'PingPong';
    }

    // Shift = Continue
    if (ScriptUI.environment.keyboardState.shiftKey) {
      loopType = 'Continue';
    }

    // Alt = Offset
    if (ScriptUI.environment.keyboardState.altKey) {
      loopType = 'Offset';
    }

    app.beginUndoGroup('Quickloop: ' + loopDirection);

    if (loopDirection === 'infinite') {
      forSelectedProperties(composition, function(property) {
        if (!property.canSetExpression) {
          return;
        }

        property.expression = 'loopIn("' + loopType + '") + loopOut("' + loopType + '") - value;';
      });
    } else {
      forSelectedProperties(composition, function(property) {
        addLoopExpression(property, loopDirection, loopType);
      });
    }

    app.endUndoGroup();
  }

    /**
    * SVG Strings 
    **/

    var iconVectors = {
      outVec: [
        '31.600000381469727,16.699999809265137 33.5,20 32.70000076293945,21.100000381469727 31.899999618530273,22.100000381469727 30.5,23.399999618530273 28.399999618530273,24.799999237060547 26.200000762939453,25.600000381469727 24.200000762939453,25.700000762939453 22.200000762939453,25.5 20.200000762939453,24.899999618530273 18.399999618530273,24 16.899999618530273,22.700000762939453 15.600000381469727,21.200000762939453 14.699999809265137,19.399999618530273 14.100000381469727,17.5 13.899999618530273,15.399999618530273 14.199999809265137,13.199999809265137 14.5,11.600000381469727 15.199999809265137,10 19.5,10 18.700000762939453,11.600000381469727 18.299999237060547,12.899999618530273 18,13.899999618530273 17.799999237060547,15.100000381469727 18,16.5 18.299999237060547,17.700000762939453 18.899999618530273,18.899999618530273 19.600000381469727,19.799999237060547 20.5,20.700000762939453 21.5,21.100000381469727 22.899999618530273,21.600000381469727 24.100000381469727,21.799999237060547 25.200000762939453,21.700000762939453 26.5,21.399999618530273 27.799999237060547,20.700000762939453 29,20 29.799999237060547,19 30.600000381469727,18.100000381469727 31.600000381469727,16.699999809265137',
        '37.5,12.899999618530273 38.599998474121094,11.199999809265137 39.599998474121094,10.199999809265137 40.70000076293945,9.399999618530273 41.900001525878906,8.900000095367432 42.900001525878906,8.5 44.20000076293945,8.400000095367432 45.29999923706055,8.5 46.79999923706055,9 48,9.600000381469727 48.900001525878906,10.5 49.599998474121094,11.5 50.099998474121094,12.300000190734863 50.5,13.300000190734863 50.79999923706055,14 50.79999923706055,14.899999618530273 50.79999923706055,15.800000190734863 50.599998474121094,17.100000381469727 50.099998474121094,18.100000381469727 49.599998474121094,19 48.70000076293945,20.100000381469727 47.79999923706055,20.799999237060547 46.599998474121094,21.399999618530273 45.400001525878906,21.700000762939453 44.5,21.700000762939453 43.400001525878906,21.700000762939453 42.29999923706055,21.5 41.29999923706055,20.899999618530273 40.5,20.399999618530273 39.599998474121094,19.399999618530273 38.20000076293945,18 37,16.199999809265137 35.29999923706055,13.300000190734863 34.099998474121094,11.399999618530273 32.79999923706055,9.699999809265137 31.799999237060547,8.5 30.700000762939453,7.5 29.399999618530273,6.5 28.100000381469727,5.800000190734863 26.5,5.300000190734863 24.700000762939453,5 9.899999618530273,5 9.199999809265137,5.199999809265137 8.399999618530273,5.699999809265137 8,6.400000095367432 8,7.199999809265137 8.300000190734863,8 9,8.699999809265137 9.899999618530273,9 16.100000381469727,9 25.100000381469727,9 26,9.100000381469727 26.899999618530273,9.399999618530273 27.600000381469727,9.800000190734863 28.299999237060547,10.399999618530273 29.200000762939453,11.199999809265137 30.100000381469727,12.199999809265137 30.799999237060547,13.199999809265137 31.600000381469727,14.300000190734863 32.20000076293945,15.399999618530273 32.599998474121094,16.199999809265137 33.099998474121094,17.100000381469727 33.599998474121094,18.100000381469727 34.20000076293945,18.899999618530273 35.099998474121094,20.200000762939453 36.20000076293945,21.700000762939453 37.5,23 39.099998474121094,24.299999237060547 40.900001525878906,25.100000381469727 43.20000076293945,25.700000762939453 45.20000076293945,25.700000762939453 47.20000076293945,25.299999237060547 49.099998474121094,24.5 50.70000076293945,23.399999618530273 52.099998474121094,22 53.29999923706055,20.299999237060547 54.099998474121094,18.5 54.5,16.399999618530273 54.5,14.300000190734863 54.099998474121094,12.300000190734863 53.29999923706055,10.399999618530273 52.099998474121094,8.800000190734863 50.70000076293945,7.400000095367432 49.099998474121094,6.199999809265137 47.20000076293945,5.5 45.20000076293945,5 43.599998474121094,5 41.900001525878906,5.199999809265137 40.20000076293945,5.800000190734863 38.79999923706055,6.5 37.5,7.400000095367432 36.400001525878906,8.400000095367432 35.400001525878906,9.699999809265137 34.900001525878906,10.5 37,14.199999809265137 37.5,12.899999618530273'
      ],
      inVec : [
        '30.899999618530273,14.100000381469727 29,10.800000190734863 29.700000762939453,9.699999809265137 30.5,8.600000381469727 32,7.300000190734863 34.099998474121094,6 36.29999923706055,5.199999809265137 38.20000076293945,5 40.29999923706055,5.199999809265137 42.29999923706055,5.800000190734863 44,6.800000190734863 45.599998474121094,8 46.79999923706055,9.600000381469727 47.79999923706055,11.300000190734863 48.400001525878906,13.300000190734863 48.599998474121094,15.399999618530273 48.29999923706055,17.5 47.900001525878906,19.200000762939453 47.20000076293945,20.700000762939453 43,20.700000762939453 43.79999923706055,19.200000762939453 44.20000076293945,17.899999618530273 44.5,16.799999237060547 44.599998474121094,15.699999809265137 44.5,14.300000190734863 44.20000076293945,13.100000381469727 43.599998474121094,11.899999618530273 42.900001525878906,11 41.900001525878906,10.100000381469727 41,9.600000381469727 39.5,9.100000381469727 38.400001525878906,9 37.29999923706055,9 35.900001525878906,9.300000190734863 34.70000076293945,10 33.5,10.800000190734863 32.599998474121094,11.800000190734863 31.899999618530273,12.600000381469727 30.899999618530273,14.100000381469727',
        '25,17.799999237060547 23.899999618530273,19.600000381469727 22.899999618530273,20.600000381469727 21.799999237060547,21.299999237060547 20.600000381469727,21.899999618530273 19.5,22.200000762939453 18.299999237060547,22.399999618530273 17.200000762939453,22.299999237060547 15.699999809265137,21.799999237060547 14.399999618530273,21.200000762939453 13.600000381469727,20.299999237060547 12.800000190734863,19.299999237060547 12.399999618530273,18.399999618530273 12,17.399999618530273 11.699999809265137,16.700000762939453 11.600000381469727,15.899999618530273 11.600000381469727,14.899999618530273 11.899999618530273,13.699999809265137 12.399999618530273,12.600000381469727 12.899999618530273,11.800000190734863 13.800000190734863,10.699999809265137 14.699999809265137,9.899999618530273 15.899999618530273,9.300000190734863 17,9.100000381469727 17.899999618530273,9 19,9.100000381469727 20.200000762939453,9.300000190734863 21.200000762939453,9.800000190734863 21.899999618530273,10.300000190734863 22.899999618530273,11.300000190734863 24.299999237060547,12.800000190734863 25.5,14.600000381469727 27.200000762939453,17.399999618530273 28.299999237060547,19.299999237060547 29.700000762939453,21.100000381469727 30.600000381469727,22.200000762939453 31.799999237060547,23.299999237060547 33.099998474121094,24.200000762939453 34.400001525878906,25 36,25.5 37.70000076293945,25.700000762939453 52.599998474121094,25.700000762939453 53.20000076293945,25.600000381469727 54,25 54.400001525878906,24.299999237060547 54.5,23.5 54.20000076293945,22.700000762939453 53.400001525878906,22 52.599998474121094,21.799999237060547 46.400001525878906,21.799999237060547 37.400001525878906,21.799999237060547 36.5,21.600000381469727 35.599998474121094,21.399999618530273 34.900001525878906,20.899999618530273 34.20000076293945,20.399999618530273 33.29999923706055,19.600000381469727 32.400001525878906,18.600000381469727 31.600000381469727,17.600000381469727 30.899999618530273,16.399999618530273 30.299999237060547,15.300000190734863 29.799999237060547,14.5 29.399999618530273,13.600000381469727 28.799999237060547,12.699999809265137 28.299999237060547,11.899999618530273 27.399999618530273,10.5 26.200000762939453,9.100000381469727 25,7.800000190734863 23.299999237060547,6.5 21.600000381469727,5.599999904632568 19.299999237060547,5 17.299999237060547,5.099999904632568 15.199999809265137,5.5 13.399999618530273,6.300000190734863 11.699999809265137,7.400000095367432 10.300000190734863,8.800000190734863 9.199999809265137,10.399999618530273 8.399999618530273,12.300000190734863 8,14.300000190734863 8,16.399999618530273 8.399999618530273,18.5 9.199999809265137,20.299999237060547 10.300000190734863,22 11.699999809265137,23.399999618530273 13.399999618530273,24.5 15.199999809265137,25.299999237060547 17.299999237060547,25.700000762939453 18.799999237060547,25.700000762939453 20.600000381469727,25.5 22.299999237060547,24.899999618530273 23.600000381469727,24.200000762939453 24.899999618530273,23.399999618530273 26.100000381469727,22.399999618530273 27,21.100000381469727 27.600000381469727,20.299999237060547 25.5,16.600000381469727 25,17.799999237060547'
      ],
      infiniteVec : [
        '50.20000076293945,19.5 50.900001525878906,17.899999618530273 51.29999923706055,16.299999237060547 51.599998474121094,14.100000381469727 51.400001525878906,12.100000381469727 50.79999923706055,10.100000381469727 49.79999923706055,8.300000190734863 48.5,6.800000190734863 47,5.5 45.20000076293945,4.599999904632568 43.29999923706055,4 41.20000076293945,3.799999952316284 39.29999923706055,4 37,4.800000190734863 35,6.099999904632568 33.5,7.400000095367432 32.70000076293945,8.5 31.899999618530273,9.5 33.89999961853027,12.800000190734863 34.89999961853027,11.399999618530273 35.60000038146973,10.5 36.5,9.5 37.70000076293945,8.800000190734863 38.89999961853027,8.100000381469727 40.29999923706055,7.800000190734863 41.400001525878906,7.800000190734863 42.5,7.900000095367432 42.70000076293945,7.800000190734863 43.79999923706055,8.100000381469727 45,8.699999809265137 45.900001525878906,9.5 46.79999923706055,10.5 47.29999923706055,11.399999618530273 47.79999923706055,12.399999618530273 48.099998474121094,13.699999809265137 48.099998474121094,14.600000381469727 48,15.5 47.70000076293945,16.200000762939453 47.29999923706055,17.200000762939453 46.900001525878906,18 46.099998474121094,19 45.29999923706055,19.899999618530273 44,20.5 42.5,20.799999237060547 41.400001525878906,20.799999237060547 40.099998474121094,20.700000762939453 39.099998474121094,20.299999237060547 37.89999961853027,19.799999237060547 36.79999923706055,19 35.89999961853027,18.200000762939453 34.70000076293945,16.600000381469727 34.20000076293945,16.100000381469727 32.20000076293945,18.899999618530273 32.60000038146973,19.799999237060547 33.60000038146973,21.100000381469727 34.70000076293945,22.100000381469727 36,23 37.39999961853027,23.700000762939453 39.099998474121094,24.299999237060547 40.79999923706055,24.5 42.400001525878906,24.5 44.400001525878906,24.100000381469727 46.29999923706055,23.299999237060547 48,22.100000381469727 49.400001525878906,20.700000762939453',
        '37.89999961853027,19.700000762939453 37.10000038146973,19.100000381469727 36.29999923706055,18.299999237060547 35.39999961853027,17.299999237060547 34.60000038146973,16.299999237060547 33.89999961853027,15.199999809265137 33.29999923706055,14.100000381469727 32.79999923706055,13.300000190734863 32.39999961853027,12.399999618530273 31.799999237060547,11.399999618530273 31.299999237060547,10.600000381469727 30.399999618530273,9.300000190734863 29.200000762939453,7.900000095367432 28,6.599999904632568 26.299999237060547,5.300000190734863 24.600000381469727,4.400000095367432 22.300000190734863,3.799999952316284 20.199999809265137,3.799999952316284 18.199999809265137,4.199999809265137 16.399999618530273,5 14.699999809265137,6.099999904632568 13.300000190734863,7.5 12.199999809265137,9.199999809265137 11.400000095367432,11.100000381469727 11,13.100000381469727 11,15.199999809265137 11.400000095367432,17.200000762939453 12.199999809265137,19.100000381469727 13.300000190734863,20.700000762939453 14.699999809265137,22.100000381469727 16.399999618530273,23.299999237060547 18.199999809265137,24.100000381469727 20.199999809265137,24.5 21.800000190734863,24.5 23.600000381469727,24.299999237060547 25.299999237060547,23.700000762939453 26.600000381469727,23 27.899999618530273,22.100000381469727 29.100000381469727,21.100000381469727 30,19.799999237060547 30.600000381469727,19 28.5,15.300000190734863 28,16.600000381469727 26.899999618530273,18.399999618530273 25.899999618530273,19.299999237060547 24.799999237060547,20.100000381469727 23.600000381469727,20.600000381469727 22.5,21 21.300000190734863,21.100000381469727 20.199999809265137,21 18.699999809265137,20.600000381469727 17.399999618530273,20 16.600000381469727,19 15.800000190734863,18 15.399999618530273,17.200000762939453 14.900000095367432,16.200000762939453 14.699999809265137,15.5 14.599999904632568,14.600000381469727 14.599999904632568,13.699999809265137 14.900000095367432,12.399999618530273 15.300000190734863,11.399999618530273 15.800000190734863,10.5 16.800000190734863,9.5 17.699999809265137,8.699999809265137 18.899999618530273,8.100000381469727 20,7.800000190734863 20.899999618530273,7.800000190734863 22,7.800000190734863 23.200000762939453,8.100000381469727 24.200000762939453,8.600000381469727 24.899999618530273,9.100000381469727 25.899999618530273,10.100000381469727 27.299999237060547,11.600000381469727 28.399999618530273,13.399999618530273 30.100000381469727,16.200000762939453 31.299999237060547,18.100000381469727 32.70000076293945,19.899999618530273 33.60000038146973,21 34.79999923706055,22 36,23 37.39999961853027,23.700000762939453',
        '12.800000190734863,5.199999809265137 11.699999809265137,3.200000047683716 10.900000095367432,3.799999952316284 12.800000190734863,5.199999809265137',
        '22.800000190734863,2.4000000953674316 23.799999237060547,0.20000000298023224 22.800000190734863,0 22.800000190734863,2.4000000953674316',
        '40.79999923706055,2.5 41.20000076293945,0.20000000298023224 40.20000076293945,0.20000000298023224 40.79999923706055,2.5',
        '50.599998474121094,6.199999809265137 52.599998474121094,5 51.900001525878906,4.300000190734863 50.599998474121094,6.199999809265137',
        '53.20000076293945,14 55.400001525878906,14.600000381469727 55.5,13.699999809265137 53.20000076293945,14',
        '49.70000076293945,22.700000762939453 50.70000076293945,24.799999237060547 51.5,24.200000762939453 49.70000076293945,22.700000762939453',
        '41.900001525878906,25.799999237060547 41.599998474121094,28.100000381469727 42.5,28 41.900001525878906,25.799999237060547',
        '33,23.100000381469727 32.5,25.399999618530273 33.5,25.399999618530273 33,23.100000381469727',
        '30.299999237060547,5.800000190734863 31.399999618530273,3.799999952316284 30.5,3.5 30.299999237060547,5.800000190734863',
        '23.5,26 23.5,28.299999237060547 24.5,28.200000762939453 23.5,26',
        '9.299999952316284,14.5 7,14.100000381469727 7,15.100000381469727 9.299999952316284,14.5',
        '12.900000095367432,23.799999237060547 11,25.100000381469727 11.699999809265137,25.799999237060547 12.900000095367432,23.799999237060547'
      ],
    };

    /**
     * Converts vector to points
     *
     * @param {*} vecCoord  Vector to convert
     * @returns {Array}     Coordinate array
     */
    function vecToPoints(vecCoord) {
        var points = [];
        var n;
        for (var i = 0; i < vecCoord.length; i++) {
            var eachNum = vecCoord[i].split(/[\s,]/);
            var coordinates = [];
            var sets = [];
            for (var k = 0; k < eachNum.length; k += 2) {
                sets.push(eachNum[k] + "," + eachNum[k + 1]);
            }
            for (var j = 0; j < sets.length; j++) {
                n = sets[j].split(",");
                coordinates[j] = n;
                coordinates[j][0] = (parseFloat(coordinates[j][0]));
                coordinates[j][1] = (parseFloat(coordinates[j][1]));
            }
            points.push(coordinates);
        }
        return points;
    }

    /**
     * Custom Draw function for vector drawing
     */
    function vecDraw() {
		this.graphics.drawOSControl();
		this.graphics.rectPath(0, 0, this.size[0], this.size[1]);
		this.graphics.fillPath(this.graphics.newBrush(this.graphics.BrushType.SOLID_COLOR, [0, 0, 0, 0]));
		try {
			for (var i = 0; i < this.coord.length; i++) {
				var line = this.coord[i];
				this.graphics.newPath();
				this.graphics.moveTo(line[0][0] + (this.size[0] / 2 - this.artSize[0] / 2), line[0][1] + (this.size[1] / 2 - this.artSize[1] / 2));
				for (var j = 0; j < line.length; j++) {
					this.graphics.lineTo(line[j][0] + (this.size[0] / 2 - this.artSize[0] / 2), line[j][1] + (this.size[1] / 2 - this.artSize[1] / 2));
				}
				this.graphics.fillPath(this.graphics.newBrush(this.graphics.BrushType.SOLID_COLOR, hexToArray(this.iconColor)));
			}
		} catch (e) {

		}
	}

    /**
     * Creates a ScriptUI button from a vector
     *
     * @param {Group} parentObj        Group to create button in
     * @param {String} iconVec         Icon vector
     * @param {[Number, Number]} size  Size of button to create
     * @param {Number[]} staticColor   RGBA for static
     * @param {Number[]} hoverColor    RGBA for hover
     * @returns {Button}               Created button
     */
    function buttonColorVector(parentObj, iconVec, size, staticColor, hoverColor) {
        
      /**
       * Updates a button on hover
       *
       * @param {Button} btn             Button to update
       * @param {String} iconVec         Icon vector
       * @param {Number[]} iconColor     RGBA for hover
       * @param {[Number, Number]} size  Size of button to create
       * @returns
       */
      function updateVectorButtonOnHover(btn, iconVec, iconColor, size) {
        btn.coord = vecToPoints(iconVec);
        btn.iconColor = iconColor;
        btn.artSize = size;
        btn.onDraw = vecDraw;
        return btn;
      }

    var btn = parentObj.add('button', [0, 0, size[0], size[1]]);
    btn.coord = vecToPoints(iconVec);
    btn.iconColor = staticColor;
    btn.artSize = size;
    btn.onDraw = vecDraw;

      if (hoverColor) {
        try {
          btn.addEventListener('mouseover', function() {
            updateVectorButtonOnHover(this, iconVec, hoverColor, size);
          });
          btn.addEventListener('mouseout', function() {
            updateVectorButtonOnHover(this, iconVec, staticColor, size);
          });
        } catch (err) {
          // fail silently
        }
      }

      return btn;
    }

    var win = thisObj instanceof Panel ? thisObj : new Window('palette', scriptName ,undefined, {resizeable: true});

    // set margins and alignment
    win.alignChildren = ['fill', 'fill'];
    win.margins = 5;
    win.spacing = 2;

    var grpMain = win.add('group');
    grpMain.alignChildren = ['fill', 'top'];
    grpMain.orientation = 'column';
    grpMain.margins = 2;
    grpMain.spacing = 2;
    
    // group to store lower button row
    var grpButtons = grpMain.add('group');
    grpButtons.margins = [0, 4, 0, 10];

    // group allows for the removal of button element
    var grpOutput = grpMain.add('group');
    grpOutput.alignment = ['fill', 'fill'];
    grpOutput.alignChildren = ['fill', 'fill'];

    updateButton(grpOutput);

    /**
    * Button functionality
    **/
    function updateButton(pGrp) {
        if (pGrp.children.length > 0) {
            pGrp.remove(pGrp.children[pGrp.children.length-1]);
    }

    // LoopOut button
    var btnLoopOut = buttonColorVector(pGrp, iconVectors.outVec, [60, 24], '#ffffff');
    btnLoopOut.onClick = function() {
      loopSelectedProps('loopOut');
    };
    btnLoopOut.helpTip = [
      'loopOut Cycle',
      'CMD - PingPong',
      'SHIFT - Continue',
      'ALT - Offset',
    ].join('\n');

   // LoopIn button
    var btnLoopIn = buttonColorVector(pGrp, iconVectors.inVec, [60, 24], '#ffffff');
    btnLoopIn.onClick = function() {
      loopSelectedProps('loopIn');
    };
    btnLoopIn.helpTip = [
      'loopIn Cycle',
      'CMD - PingPong',
      'SHIFT - Continue',
      'ALT - Offset',
    ].join('\n');

    // loopInfinite button
    var btnLoopInfinite = buttonColorVector(pGrp, iconVectors.infiniteVec, [60, 24], '#ffffff');
    btnLoopInfinite.onClick = function() {
      loopSelectedProps('infinite');
    };
    btnLoopInfinite.helpTip = [
      'loopIn Cycle (in both directions, forever!)',
      'CMD - PingPong',
      'SHIFT - Continue',
      'ALT - Offset',
    ].join('\n')
    
		win.layout.layout(true); // auto layout
		win.layout.resize(); // resize everything
	}

/** Hover colour for future versions: #735294 **/

  win.onResizing = win.onResize = function () {win.layout.resize();};
  if (!(win instanceof Panel)) win.show();

})(this);